﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ServiceModel;
using System.Runtime.Remoting.Messaging;
using JpLabs.CustomEvents;
using System.Threading.Tasks;
using DistributedGame.Service;
using DistributedGame.Client;
using System.Runtime.Serialization;

namespace DistributedGame
{
	//public static class GameHostCallbackExt
	//{
	//    public static IAsyncResult BeginConnect(this IGameHost host, ClientConnectRequest request)
	//    {
	//        var dlg = (Func<ClientConnectRequest,PlayerInfo>)host.Connect;
	//        return dlg.BeginInvoke(request, null, null);
	//    }
		
	//    public static PlayerInfo EndConnect(this IGameHost host, IAsyncResult asyncResult)
	//    {
	//        asyncResult.AsyncWaitHandle.WaitOne();
			
	//        var dlg = (Func<ClientConnectRequest,PlayerInfo>)((AsyncResult)asyncResult).AsyncDelegate;
	//        return dlg.EndInvoke(asyncResult);
	//    }
	//}

	/// <summary>
	/// Interaction logic for ClientPage.xaml
	/// </summary>
	[CallbackBehavior(
		ConcurrencyMode=ConcurrencyMode.Reentrant
		,UseSynchronizationContext = false
	)]
	public partial class ClientPage : Page, IGameClientCallback, IDisposable
	{
		public Uri RemoteHostAddress { get; set; }
		
		public GameClient GameClient { get; private set; }

		public CommunicationState ClientState
		{
			get {
				var gameClient = this.GameClient;
				if (gameClient == null) return CommunicationState.Closed;
				return gameClient.State;
			}
		}
		
		public ClientPage()
		{
			InitializeComponent();

			txtChatArea.Document.LineHeight = 1;

			//this.Unloaded += new RoutedEventHandler(ClientPage_Unloaded);
			Application.Current.Exit += CustomEvent.ToWeak<ExitEventHandler>(CurrentApplication_Exit);

			AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
		}

		void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
		{
			System.Diagnostics.Debug.WriteLine("Unhadled Exception: {0}", e.ExceptionObject.ToString());
		}

		private void CurrentApplication_Exit(object sender, ExitEventArgs e)
		{
			Dispose();
		}

		//private void ClientPage_Unloaded(object sender, RoutedEventArgs e)
		//{
		//    Dispose();
		//}

		public void Dispose()
		{
			var gameClient = this.GameClient;

			if (gameClient != null) gameClient.Dispose();

			this.GameClient = null;
		}
		
		private void txtChat_KeyDown(object sender, KeyEventArgs e)
		{
			if (e.Key == Key.Enter) {
				string text = txtChat.Text;
				txtChat.Text = null;
				
				TextEntered(text);
			}
		}
		
		private void btnConnect_Click(object sender, RoutedEventArgs e)
		{
			var state = this.ClientState;
			switch (state)
			{
				case CommunicationState.Closed: ConnectTo(this.RemoteHostAddress); break;
				case CommunicationState.Created: break;
				//case CommunicationState.Opened:
				default: Disconnect(); break;
			}
			
			UpdateUI();
		}

		private void ConnectTo(Uri remoteHostAddress)
		{
			GameClient = new GameClient();
			GameClient.StateChanged += CustomEvent.ToWeakSynced(this, (EventHandler<ValueChangedEventArgs<CommunicationState>>)GameClient_StateChanged);
			
			//var clientInfo = GameClient.Connect(remoteHostAddress, txtName.Text, this);
			//AddChatText("Connected to " + this.RemoteHostAddress);
			//AddChatText("as '" + clientInfo.Nickname + "'");

			AddChatText("Conecting...");

			string desiredNickname = txtName.Text;
			Task.Factory.StartNew(
				() => GameClient.Connect(remoteHostAddress, desiredNickname, this)
			)
			.ContinueWith(
				t => {
					if (t.IsFaulted) {
						this.AddChatText("Error connecting: " + t.Exception.InnerException.Message);
					} else {
						this.AddChatText(string.Format("Connected to {0} as '{1}'", remoteHostAddress, t.Result.Nickname));
					}

					UpdateUI();
				}
			);
		}

		private void Disconnect()
		{
			this.Dispose();
			
			this.AddChatText("* Disconnected");
		}

		private void GameClient_StateChanged(object sender, ValueChangedEventArgs<CommunicationState> e)
		{
			//AddChatText("* Channel " + e.NewValue);
			UpdateUI();
		}
		
		private void UpdateUI()
		{
			if (!this.Dispatcher.CheckAccess()) {
				this.Dispatcher.Invoke((Action)this.UpdateUI);
				return;
			}

			var state = this.ClientState;

			txtName.IsReadOnly = true;
			btnConnectDisconnect.IsEnabled = false;

			switch (state)
			{
				case CommunicationState.Closed: {
					btnConnectDisconnect.Content = "Connect";
					btnConnectDisconnect.IsEnabled = true;
					txtName.IsReadOnly = false;
				} break;

				case CommunicationState.Created:
				case CommunicationState.Opening: {
					btnConnectDisconnect.Content = "Connecting...";
				} break;

				case CommunicationState.Opened: {
					btnConnectDisconnect.Content = "Disconnect";
					btnConnectDisconnect.IsEnabled = true;
				} break;

				case CommunicationState.Faulted: {
					btnConnectDisconnect.Content = "Faulted - Dispose";
					btnConnectDisconnect.IsEnabled = true;
				} break;

				default: {
					btnConnectDisconnect.Content = state.ToString();
				} break;
			}

		}

		private void TextEntered(string text)
		{
			if (this.ClientState == CommunicationState.Opened) {
				GameClient.GameHost.Say(text);
			} else {
				AddChatText("[offline] " + text);
			}
		}
		
		private void AddChatText(string text)
		{
			if (!this.Dispatcher.CheckAccess()) {
				this.Dispatcher.Invoke((Action<string>)this.AddChatText, text);
				return;
			}

			txtChatArea.AppendText(text + Environment.NewLine);
		}

		void IGameClientCallback.Receive(RemoteMessage message)
		{
			//AddChatText(msg);
			
			var text = MessageFormatter.Message(message);

			this.Dispatcher.InvokeIfNeeded( (Action<string>)AddChatText, text );
		}

		void IGameClientCallback.RefreshClients(IList<ClientConnectionInfo> clients)
		{
			lstPlayerList.ItemsSource = clients.Select( c => c.Nickname ).ToArray();
		}

		void IGameClientCallback.ServerClosing()
		{
			this.Dispatcher.InvokeIfNeeded( (Action<string>)AddChatText, "* Host is closing" );

			//WCF doesn't closes the channel on the client side for some reason (reconnect?)
			this.GameClient.GameChannel.Close();
		}
	}
}
